///\todo upgrade

//////////////////////////////////////////////////////////////////////////////////
//                                                                              //
//  This file is part of the buola project (https://code.google.com/p/buola/).  //
//                                                                              //
//  Copyright(c) 2007-2012 Xavi Gratal                                          //
//  gratal AT gmail DOT com                                                     //
//                                                                              //
//  Buola is free software: you can redistribute it and/or modify               //
//  it under the terms of the GNU General Public License as published by        //
//  the Free Software Foundation, either version 3 of the License, or           //
//  (at your option) any later version.                                         //
//                                                                              //
//  Buola is distributed in the hope that it will be useful,                    //
//  but WITHOUT ANY WARRANTY; without even the implied warranty of              //
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the               //
//  GNU General Public License for more details.                                //
//                                                                              //
//  You should have received a copy of the GNU General Public License           //
//  along with buola.  If not, see <http://www.gnu.org/licenses/>.              //
//                                                                              //
//////////////////////////////////////////////////////////////////////////////////

#ifndef _BUOLA_UTILITY_UEVENT_H_
#define _BUOLA_UTILITY_UEVENT_H_

#include <buola/utility/usignal.h>
#include <memory>
#include <buola/traits/tuple.h>
#include <buola/utility/placeholders.h>

namespace buola {

struct triggered_event
{
    triggered_event()
    {
    }

    virtual ~triggered_event()
    {
    }

    virtual void Run()=0;
};

template<typename tSignature>
struct typed_triggered_event;

template<typename tResult,typename... tArgs>
struct typed_triggered_event<tResult(tArgs...)> : public triggered_event
{
    typedef std::shared_ptr<USignal<tResult(tArgs...)>> tSignal;

    template<typename... tPArgs>
    typed_triggered_event(tSignal pSignal,tPArgs&&... pArgs)
        :   mSignal(pSignal)
        ,   mArgs(std::forward<tPArgs>(pArgs)...)
    {}

    template<std::size_t... pIndices>
    void MakeFunctionCall(const MIndexTuple<pIndices...>&)
    {
        mSignal->operator()(std::get<pIndices>(mArgs)...);
    }

    virtual void Run()
    {
        typedef typename MMakeIndices<sizeof...(tArgs)>::type tIndices;
        return MakeFunctionCall(tIndices());
    }

    tSignal mSignal;
    std::tuple<tArgs...> mArgs;
};

void store_triggered_event(triggered_event *pE);

///\ingroup core signals
///@{

///////////////////////////////////////////////////////////////////////////////

/// event (asynchronous signal)

/// Events work in the same way as signals (class boost::signal), except that
/// connected callbacks are not called immediately when the event is triggered.
/// Instead, they are called when control returns to the main loop.
///
/// This has some advantages:
/// - They can be triggered from any thread, and the callback will always be
/// called from the thread which runs the main loop.
/// - It is safe to delete the object which emits the signal from the
/// callback.
///
/// And some disadvantages
/// - There is some performance overhead
/// - It is not possible to get return values from the callbacks
///
///////////////////////////////////////////////////////////////////////////////

template<typename tSignature>
class UEvent
{
    typedef std::shared_ptr<USignal<tSignature>> tSignal;

public:
    //! default (and only) constructor
    UEvent()
    {
    }

    //! registers a callback for the event
    template<typename tArg>
    void Connect(tArg&& pArg)
    {
        if(!mSignal)
            mSignal.reset(new USignal<tSignature>);
        mSignal->Connect(std::forward<tArg>(pArg));
    }

    template<typename tRet,typename tClass,typename tObj,typename... tFArgs,typename... tArgs>
    void Connect(tRet(tClass::*pFn)(tFArgs...),tObj *pObj,tArgs&&... pArgs)
    {
        static_assert(sizeof...(tFArgs)==sizeof...(tArgs),"function parameter list must have same size as arguments");
        return Connect(std::bind(pFn,pObj,std::forward<tArgs>(pArgs)...));
    }

    template<typename tRet,typename tClass,typename tObj,typename... tFArgs,typename... tArgs>
    void Connect(tRet(tClass::*pFn)(tFArgs...),const intrusive_ptr<tObj> &pObj,tArgs&&... pArgs)
    {
        static_assert(sizeof...(tFArgs)==sizeof...(tArgs),"function parameter list must have same size as arguments");
        return Connect(std::bind(pFn,pObj.get(),std::forward<tArgs>(pArgs)...));
    }

    //!triggers the event
    template<typename... tArgs>
    void operator()(tArgs&&... pArgs)
    {
        if(!mSignal) return;
        triggered_event *lEvent=new typed_triggered_event<tSignature>(mSignal,std::forward<tArgs>(pArgs)...);
        store_triggered_event(lEvent);
    }

private:
    tSignal mSignal;
};

///@}

/*namespace buola*/ }

#endif
